#include "wx/wxprec.h"
#if wxUSE_HELP

#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/module.h"
#endif

#include "wx/tipwin.h"
#include "wx/cshelp.h"

class wxContextHelpEvtHandler: public wxEvtHandler {
  public:
    wxContextHelpEvtHandler( wxContextHelp* contextHelp ) {
      m_contextHelp = contextHelp;
    }

    virtual bool ProcessEvent( wxEvent& event );

    //// Data
    wxContextHelp* m_contextHelp;

    DECLARE_NO_COPY_CLASS( wxContextHelpEvtHandler )
};

IMPLEMENT_DYNAMIC_CLASS( wxContextHelp, wxObject )

wxContextHelp::wxContextHelp( wxWindow* win, bool beginHelp ) {
  m_inHelp = false;
  if( beginHelp ) {
    BeginContextHelp( win );
  }
}

wxContextHelp::~wxContextHelp() {
  if( m_inHelp ) {
    EndContextHelp();
  }
}

#ifdef __WXMOTIF__
static void wxPushOrPopEventHandlers( wxContextHelp* help, wxWindow* win, bool push ) {
  if( push ) {
    win->PushEventHandler( new wxContextHelpEvtHandler( help ) );
  } else
  { win->PopEventHandler( true ); }
  wxWindowList::compatibility_iterator node = win->GetChildren().GetFirst();
  while( node ) {
    wxWindow* child = node->GetData();
    wxPushOrPopEventHandlers( help, child, push );
    node = node->GetNext();
  }
}
#endif

bool wxContextHelp::BeginContextHelp( wxWindow* win ) {
  if( !win ) {
    win = wxTheApp->GetTopWindow();
  }
  if( !win ) {
    return false;
  }
  wxCursor cursor( wxCURSOR_QUESTION_ARROW );
  wxCursor oldCursor = win->GetCursor();
  win->SetCursor( cursor );
  m_status = false;
  #ifdef __WXMOTIF__
  wxPushOrPopEventHandlers( this, win, true );
  #else
  win->PushEventHandler( new wxContextHelpEvtHandler( this ) );
  #endif
  win->CaptureMouse();
  EventLoop();
  win->ReleaseMouse();
  #ifdef __WXMOTIF__
  wxPushOrPopEventHandlers( this, win, false );
  #else
  win->PopEventHandler( true );
  #endif
  win->SetCursor( oldCursor );
  if( m_status ) {
    wxPoint pt;
    wxWindow* winAtPtr = wxFindWindowAtPointer( pt );
    if( winAtPtr ) {
      DispatchEvent( winAtPtr, pt );
    }
  }
  return true;
}

bool wxContextHelp::EndContextHelp() {
  m_inHelp = false;
  return true;
}

bool wxContextHelp::EventLoop() {
  m_inHelp = true;
  while( m_inHelp ) {
    if( wxTheApp->Pending() ) {
      wxTheApp->Dispatch();
    } else
    { wxTheApp->ProcessIdle(); }
  }
  return true;
}

bool wxContextHelpEvtHandler::ProcessEvent( wxEvent& event ) {
  if( event.GetEventType() == wxEVT_LEFT_DOWN ) {
    m_contextHelp->SetStatus( true );
    m_contextHelp->EndContextHelp();
    return true;
  }
  if( ( event.GetEventType() == wxEVT_CHAR ) ||
      ( event.GetEventType() == wxEVT_KEY_DOWN ) ||
      ( event.GetEventType() == wxEVT_ACTIVATE ) ||
      ( event.GetEventType() == wxEVT_MOUSE_CAPTURE_CHANGED ) ) {
    m_contextHelp->EndContextHelp();
    return true;
  }
  if( ( event.GetEventType() == wxEVT_PAINT ) ||
      ( event.GetEventType() == wxEVT_ERASE_BACKGROUND ) ) {
    event.Skip();
    return false;
  }
  return true;
}

bool wxContextHelp::DispatchEvent( wxWindow* win, const wxPoint& pt ) {
  wxCHECK_MSG( win, false, _T( "win parameter can't be NULL" ) );
  wxHelpEvent helpEvent( wxEVT_HELP, win->GetId(), pt,
                         wxHelpEvent::Origin_HelpButton );
  helpEvent.SetEventObject( win );
  return win->GetEventHandler()->ProcessEvent( helpEvent );
}


#ifndef __WXPM__

static const char * csquery_xpm[] = {
  "12 11 2 1",
  "  c None",
  ". c #000000",
  "            ",
  "    ....    ",
  "   ..  ..   ",
  "   ..  ..   ",
  "      ..    ",
  "     ..     ",
  "     ..     ",
  "            ",
  "     ..     ",
  "     ..     ",
  "            "
};

#endif

IMPLEMENT_CLASS( wxContextHelpButton, wxBitmapButton )

BEGIN_EVENT_TABLE( wxContextHelpButton, wxBitmapButton )
  EVT_BUTTON( wxID_CONTEXT_HELP, wxContextHelpButton::OnContextHelp )
END_EVENT_TABLE()

wxContextHelpButton::wxContextHelpButton( wxWindow* parent,
    wxWindowID id,
    const wxPoint& pos,
    const wxSize& size,
    long style )
#if defined(__WXPM__)
  : wxBitmapButton( parent, id, wxBitmap( wxCSQUERY_BITMAP
                                          , wxBITMAP_TYPE_RESOURCE
                                        ),
                    pos, size, style )
#else
  : wxBitmapButton( parent, id, wxBitmap( csquery_xpm ),
                    pos, size, style )
#endif
{
}

void wxContextHelpButton::OnContextHelp( wxCommandEvent& ( event ) ) {
  wxContextHelp contextHelp( GetParent() );
}

wxHelpProvider *wxHelpProvider::ms_helpProvider = ( wxHelpProvider * )NULL;

void wxHelpProvider::AddHelp( wxWindowBase * ( window ),
                              const wxString& ( text ) ) {
}

void wxHelpProvider::AddHelp( wxWindowID ( id ),
                              const wxString& ( text ) ) {
}

void wxHelpProvider::RemoveHelp( wxWindowBase* ( window ) ) {
}

wxHelpProvider::~wxHelpProvider() {
}

wxString wxHelpProvider::GetHelpTextMaybeAtPoint( wxWindowBase *window ) {
  if( m_helptextAtPoint != wxDefaultPosition ||
      m_helptextOrigin != wxHelpEvent::Origin_Unknown ) {
    wxCHECK_MSG( window, wxEmptyString, _T( "window must not be NULL" ) );
    wxPoint pt = m_helptextAtPoint;
    wxHelpEvent::Origin origin = m_helptextOrigin;
    m_helptextAtPoint = wxDefaultPosition;
    m_helptextOrigin = wxHelpEvent::Origin_Unknown;
    return window->GetHelpTextAtPoint( pt, origin );
  }
  return GetHelp( window );
}

// ----------------------------------------------------------------------------
// wxSimpleHelpProvider
// ----------------------------------------------------------------------------

#define WINHASH_KEY(w) wxPtrToUInt(w)

wxString wxSimpleHelpProvider::GetHelp( const wxWindowBase *window ) {
  wxSimpleHelpProviderHashMap::iterator it = m_hashWindows.find( WINHASH_KEY( window ) );
  if( it == m_hashWindows.end() ) {
    it = m_hashIds.find( window->GetId() );
    if( it == m_hashIds.end() ) {
      return wxEmptyString;
    }
  }
  return it->second;
}

void wxSimpleHelpProvider::AddHelp( wxWindowBase *window, const wxString& text ) {
  m_hashWindows.erase( WINHASH_KEY( window ) );
  m_hashWindows[WINHASH_KEY( window )] = text;
}

void wxSimpleHelpProvider::AddHelp( wxWindowID id, const wxString& text ) {
  wxSimpleHelpProviderHashMap::key_type key = ( wxSimpleHelpProviderHashMap::key_type )id;
  m_hashIds.erase( key );
  m_hashIds[key] = text;
}

// removes the association
void wxSimpleHelpProvider::RemoveHelp( wxWindowBase* window ) {
  m_hashWindows.erase( WINHASH_KEY( window ) );
}

bool wxSimpleHelpProvider::ShowHelp( wxWindowBase *window ) {
  #if wxUSE_MS_HTML_HELP || wxUSE_TIPWINDOW
  #if wxUSE_MS_HTML_HELP
  // m_helptextAtPoint will be reset by GetHelpTextMaybeAtPoint(), stash it
  const wxPoint posTooltip = m_helptextAtPoint;
  #endif // wxUSE_MS_HTML_HELP
  const wxString text = GetHelpTextMaybeAtPoint( window );
  if( !text.empty() ) {
    // use the native help popup style if it's available
    #if wxUSE_MS_HTML_HELP
    if( !wxCHMHelpController::ShowContextHelpPopup
        (
          text,
          posTooltip,
          ( wxWindow * )window
        ) )
    #endif // wxUSE_MS_HTML_HELP
    {
      #if wxUSE_TIPWINDOW
      static wxTipWindow* s_tipWindow = NULL;
      if( s_tipWindow ) {
        // Prevent s_tipWindow being nulled in OnIdle, thereby removing
        // the chance for the window to be closed by ShowHelp
        s_tipWindow->SetTipWindowPtr( NULL );
        s_tipWindow->Close();
      }
      s_tipWindow = new wxTipWindow( ( wxWindow * )window, text,
                                     100, &s_tipWindow );
      #else // !wxUSE_TIPWINDOW
      // we tried wxCHMHelpController but it failed and we don't have
      // wxTipWindow to fall back on, so
      return false;
      #endif // wxUSE_TIPWINDOW
    }
    return true;
  }
  #else // !wxUSE_MS_HTML_HELP && !wxUSE_TIPWINDOW
  wxUnusedVar( window );
  #endif // wxUSE_MS_HTML_HELP || wxUSE_TIPWINDOW
  return false;
}

// ----------------------------------------------------------------------------
// wxHelpControllerHelpProvider
// ----------------------------------------------------------------------------

wxHelpControllerHelpProvider::wxHelpControllerHelpProvider( wxHelpControllerBase* hc ) {
  m_helpController = hc;
}

bool wxHelpControllerHelpProvider::ShowHelp( wxWindowBase *window ) {
  const wxString text = GetHelpTextMaybeAtPoint( window );
  if( text.empty() ) {
    return false;
  }
  if( m_helpController ) {
    // if it's a numeric topic, show it
    long topic;
    if( text.ToLong( &topic ) ) {
      return m_helpController->DisplayContextPopup( topic );
    }
    // otherwise show the text directly
    if( m_helpController->DisplayTextPopup( text, wxGetMousePosition() ) ) {
      return true;
    }
  }
  // if there is no help controller or it's not capable of showing the help,
  // fallback to the default method
  return wxSimpleHelpProvider::ShowHelp( window );
}

// Convenience function for turning context id into wxString
wxString wxContextId( int id ) {
  return wxString::Format( _T( "%d" ), id );
}

// ----------------------------------------------------------------------------
// wxHelpProviderModule: module responsible for cleaning up help provider.
// ----------------------------------------------------------------------------

class wxHelpProviderModule : public wxModule {
  public:
    bool OnInit();
    void OnExit();

  private:
    DECLARE_DYNAMIC_CLASS( wxHelpProviderModule )
};

IMPLEMENT_DYNAMIC_CLASS( wxHelpProviderModule, wxModule )

bool wxHelpProviderModule::OnInit() {
  // Probably we don't want to do anything by default,
  // since it could pull in extra code
  // wxHelpProvider::Set(new wxSimpleHelpProvider);
  return true;
}

void wxHelpProviderModule::OnExit() {
  if( wxHelpProvider::Get() ) {
    delete wxHelpProvider::Get();
    wxHelpProvider::Set( NULL );
  }
}

#endif // wxUSE_HELP
